package coap.server;

import coap.Configuration;
import coap.client.CoAPClient;
import coap.messages.Message;
import coap.messages.MessageType;
import coap.messages.MethodCodes;
import coap.network.MessageHandler;
import coap.network.TimeoutChecker;
import coap.network.TimeoutHandler;
import coap.network.UDPNetwork;

import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.HashMap;

/**
 * Created by IntelliJ IDEA.
 * User: iodine
 * Date: Jan 21, 2012
 * Time: 7:16:45 PM
 * To change this template use File | Settings | File Templates.
 */
public class ServerSession implements MessageHandler, TimeoutHandler {
    UDPNetwork network = null;
    HashMap<Integer, Message> outstandingQueue = new HashMap<Integer, Message>();
    TimeoutChecker checker = null;
    CoAPServer server;


    public ServerSession(CoAPServer server) {
        this.server = server;
        try {
            checker = new TimeoutChecker(this);
            checker.start();
            network = new UDPNetwork(Configuration.PORT, this);
            network.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendMessage(Message msg, InetAddress address, int port) {
        if (msg.getType() == MessageType.Confirmable)
            outstandingQueue.put(msg.getMessageID(), msg);
        byte[] b = new byte[Configuration.MAX_PACKET_SIZE];
        ByteBuffer buf = ByteBuffer.wrap(b);
        msg.serialize(buf);
        network.send(b, 0, buf.position(), address, port);
        if (msg.getType() == MessageType.Confirmable)
            checker.add(msg);
    }


    public synchronized void handleMessage(byte[] b, int len, InetAddress sender, int senderPort) {
        ByteBuffer buf = ByteBuffer.wrap(b, 0, len);
        Message msg = new Message(buf);
        msg.setSender(sender, senderPort);
        switch (msg.getType()) {
            case MessageType.Confirmable:
                if (!msg.isRequest())
                    throw new RuntimeException("weird");
                server.handleRequest(msg, sender, senderPort);
                break;
            case MessageType.NonConfirmable:
                if (!msg.isRequest())
                    throw new RuntimeException("weird");
                server.handleRequest(msg, sender, senderPort);
                break;
            case MessageType.Acknowledgement:
                if (msg.getCode() != 0) { //piggybacked
                    throw new RuntimeException("werid. client should not send piggyback to server");
                } else {
                    Message messageAcked = outstandingQueue.remove(msg.getMessageID());
                    if (messageAcked == null) {
                        System.out.println("receive an unknown ack " + msg);
                        System.out.println("silently ignored");
                    } else {
                        checker.remove(messageAcked);
                        server.handleAck(msg);
                    }
                }
                break;
            case MessageType.Reset:
                System.out.println("Unexpected reset message");
                outstandingQueue.remove(msg.getMessageID());
                break;
        }
    }

    public synchronized void timeout(Message msg, boolean overLimit) {
        if(msg.getType()!=MessageType.Confirmable)
            throw new RuntimeException("weird");
        if(!overLimit)
            sendMessage(msg, msg.getSender(), msg.getSenderPort());
        else{
            this.outstandingQueue.remove(msg);
            server.failed(msg);
        }
    }
}